#!/usr/bin/env python
# -*- coding: UTF-8 -*-

# chat_window.py 
#
#  Copyright (c) 2007 INdT (Instituto Nokia de Technologia)
#                2007 Kenneth Rohde Christiansen
#
#  Portions of the code, copyright (c) the Gajim Project (GPL)
#
#  Author: Kenneth Rohde Christiansen <kenneth.christiansen@gmail.com>
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License as
#  published by the Free Software Foundation; either version 2 of the
#  License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
#  USA

import pygtk
pygtk.require("2.0")
import gtk
import gobject
import pango
import tapioca
import icon_theme
import os
import time
import datetime
import util
from tapioca import AudioStream
from message_logger import MessageLogger
from conversation_buffer import ConversationBuffer
from volume_control import VolumeControl

from gettext import gettext as _

class ChatWindow(gobject.GObject):

    (
    CHANNEL_TEXT,
    CHANNEL_STREAM
    ) = range(2)

    (
    VIEW_NONE,
    VIEW_CALLING,
    VIEW_RECEIVING_CALL,
    VIEW_TALKING
    ) = range(4)

    TEXT_SCROLLING_THRESHOLD = 30
    CONVERSATION_TIMEOUT = 30000
    COMPOSING_TIMEOUT = 5000

    __gsignals__ = {
        'chat-window-closed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                    (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT))
    }


    def __init__(self, channel, local_contact_store, main_app, last_messages=[]):
        gobject.GObject.__init__(self)

        self.last_messages = last_messages
        self.last_time = None
        self.backlog = last_messages
        self.message_logging = True

        self.main_app = main_app

        self.capabilities = None
        self.channels = [None, None]
        self.add_channel(channel)

        contact = self.get_contact()
        self.contact_id = contact.get_uri()

        self.msg_logger = MessageLogger(self.contact_id)

        self.local_contact_store = local_contact_store
        self.local_contact_store.connect("contact-store-updated", \
                self.on_contact_store_updated)

        self.last_who_talked = None
        self.conversation_timeout_handler = None
        self.composing_timeout_handler = None
        self.contact_custom_presence_message = \
                self.local_contact_store.get_custom_presence_message_only(self.contact_id)

        self.chat_window = gtk.Window(gtk.WINDOW_TOPLEVEL)
        self.chat_window.set_property("icon-name", 'ereseva')
        self.chat_window.set_title(_("%s - Chat") % \
                self.local_contact_store.get_alias(self.contact_id))
        self.chat_window.resize(400, 300)

        self.vbox1 = gtk.VBox(False, 0)
        self.chat_window.add(self.vbox1)
        self.vbox1.set_border_width(3)

        self.alignment2 = gtk.Alignment(0.5, 0.5, 1, 1)
        self.alignment2.set_padding(0, 3, 0, 0)

        self.vbox1.pack_start(self.alignment2, False, True, 0)

        self.hbox1 = gtk.HBox(False, 0)
        self.alignment2.add(self.hbox1)
        self.hbox1.set_border_width(3)

        self.status_image = gtk.Image()
        self.hbox1.pack_start(self.status_image, False, False, 0)

        self.contact_label = gtk.Label("<b>%s</b>" % \
                self.local_contact_store.get_alias(self.contact_id))
        self.hbox1.pack_start(self.contact_label, False, False, 0)
        self.contact_label.set_use_markup(True)
        self.contact_label.set_padding(6, 0)

        self.scrolled_conversation_window = gtk.ScrolledWindow(None, None)
        self.vbox1.pack_start(self.scrolled_conversation_window, True, True, 0)
        self.scrolled_conversation_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        self.scrolled_conversation_window.set_shadow_type(gtk.SHADOW_IN)

        self.conversation_buffers = ConversationBuffer(self.main_app.emoticons)

        conversation_textbuffer = self.conversation_buffers.get_text_buffer()
        conversation_textbuffer.connect_after("mark-set", self.on_mark_set)

        self.conversation_textview = gtk.TextView(conversation_textbuffer)
        self.conversation_textview.set_wrap_mode(gtk.WRAP_WORD)
        self.conversation_textview.set_editable(False)
        self.conversation_textview.set_cursor_visible(False)
        self.conversation_textview.set_left_margin(4)
        self.conversation_textview.set_right_margin(4)
        self.conversation_textview.connect("copy-clipboard", self.on_copy_clipboard)
        self.conversation_textview.connect("motion-notify-event", self.on_motion_notify_event)

        self.scrolled_conversation_window.add(self.conversation_textview)

        self.info_label = gtk.Label()
        self.vbox1.pack_start(self.info_label, False, False, 3)
        self.info_label.set_use_markup(True)
        self.info_label.set_line_wrap(True)

        self.scrolled_typing_window = gtk.ScrolledWindow(None, None)
        self.vbox1.pack_start(self.scrolled_typing_window, False, True, 0)
        self.scrolled_typing_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        self.scrolled_typing_window.set_shadow_type(gtk.SHADOW_IN)

        self.typing_textview = gtk.TextView()
        self.typing_textview.set_left_margin(4)
        self.typing_textview.set_right_margin(4)
        self.typing_textview.set_accepts_tab(False)
        self.typing_textview.set_wrap_mode(gtk.WRAP_WORD)
        self.typing_textview.connect("paste-clipboard", self.on_paste_clipboard)
        self.typing_textview.connect("button_press_event", self.on_button_press_event)
        self.scrolled_typing_window.add(self.typing_textview)
        self.vbox1.set_focus_chain( (self.typing_textview, ) )

        self.typing_textview.connect('key-press-event', self.send_message)

        self.alignment1 = gtk.Alignment(0.5, 0.5, 1, 1)
        self.alignment1.set_padding(3, 0, 0, 0)

        self.alignment2 = gtk.Alignment(0.5, 0.5, 1, 1)
        self.alignment2.set_padding(3, 0, 0, 0)

        self.button_box_left = gtk.HButtonBox()
        self.button_box_left.set_layout(gtk.BUTTONBOX_START)
        self.alignment1.add(self.button_box_left)

        self.buttonbar_hbox = gtk.HBox(spacing=2)
        self.vbox1.pack_start(self.buttonbar_hbox, False, True, 0)
        self.buttonbar_hbox.pack_start(self.alignment1, False, True, 0)
        self.buttonbar_hbox.pack_end(self.alignment2, False, True, 0)

        itheme = icon_theme.icon_theme_get_default()

        icon = gtk.Image()
        icon.set_from_pixbuf(itheme.load_icon('gtk-properties', 16, 0),)

        self.options_button = gtk.Button()
        self.options_button.set_flags(gtk.CAN_DEFAULT)
        self.options_button.set_relief(gtk.RELIEF_NONE)

        options_hbox = gtk.HBox(spacing=2)
        options_icon = gtk.image_new_from_stock(gtk.STOCK_PROPERTIES, \
                gtk.ICON_SIZE_BUTTON)

        options_label = gtk.Label(_("Options"))
        options_arrow = gtk.Arrow(gtk.ARROW_DOWN, gtk.SHADOW_OUT)
        options_hbox.pack_start(options_icon, expand=False, fill=True)
        options_hbox.pack_start(options_label, expand=True, fill=True)
        options_hbox.pack_start(options_arrow, expand=False, fill=True)

        self.options_button.add(options_hbox)
        self.options_button.connect('clicked', self.on_options_button_clicked)

        self.button_box_left.add(self.options_button)

        hang_up_image = gtk.Image()
        hang_up_image.set_from_pixbuf(itheme.load_icon("phone-hang-up", 16, 0))
        pick_up_image = gtk.Image()
        pick_up_image.set_from_pixbuf(itheme.load_icon("phone-pick-up", 16, 0))

        self.volume_control = VolumeControl()

        self.call_button = gtk.Button()
        self.call_button.set_label(_("Call"))
        self.call_button.set_image(pick_up_image)
        self.call_button.connect('clicked', self.on_call_button_clicked)

        self.endcall_button = gtk.Button()
        self.endcall_button.set_label(_("End Call"))
        self.endcall_button.set_image(hang_up_image)
        self.endcall_button.connect('clicked', self.on_endcall_button_clicked)

        hang_up_image = gtk.Image()
        hang_up_image.set_from_pixbuf(itheme.load_icon("phone-hang-up", 16, 0))
        pick_up_image = gtk.Image()
        pick_up_image.set_from_pixbuf(itheme.load_icon("phone-pick-up", 16, 0))

        self.answer_button = gtk.Button()
        self.answer_button.set_label(_("Answer"))
        self.answer_button.set_image(pick_up_image)
        self.answer_button.connect('clicked', self.on_answer_button_clicked)

        self.ignore_button = gtk.Button()
        self.ignore_button.set_label(_("Ignore"))
        self.ignore_button.set_image(hang_up_image)
        self.ignore_button.connect('clicked', self.on_ignore_button_clicked)

        self.calling_label = gtk.Label()
        self.calling_label.set_alignment(0.05, 0.5)
        self.calling_label.set_markup("<small>%s</small>" %\
                _("Calling..."))

        self.hbox1.pack_end(self.volume_control, False, False)
        self.hbox1.pack_end(self.call_button, False, False)
        self.hbox1.pack_end(self.endcall_button, False, False)
        self.hbox1.pack_end(self.answer_button, False, False)
        self.hbox1.pack_end(self.ignore_button, False, False)
        self.hbox1.pack_end(self.calling_label, False, False)

        # kill me
        #self.hbox1.pack_end(self.volume_button, False, False)

        #self.button_box_left.add(self.call_button)
        #self.button_box_left.add(self.endcall_button)

        self.emoticon_button = gtk.Button()
        emoticon_hbox = gtk.HBox(spacing=2)
        emoticon_pixbuf = self.main_app.emoticons.get_emoticon_pixbufs()[':-)']

        emoticon_icon = gtk.Image()
        emoticon_icon.set_from_pixbuf(emoticon_pixbuf)

        emoticon_arrow = gtk.Arrow(gtk.ARROW_DOWN, gtk.SHADOW_OUT)
        emoticon_hbox.pack_start(emoticon_icon, expand=False, fill=True)
        emoticon_hbox.pack_start(emoticon_arrow, expand=False, fill=True)
        self.emoticon_button.add(emoticon_hbox)
        self.emoticon_button.connect('clicked', self.on_emoticon_button_clicked)

        self.alignment2.add(self.emoticon_button)
        self.emoticon_button.set_flags(gtk.CAN_DEFAULT)
        self.emoticon_button.set_relief(gtk.RELIEF_NONE)

        self.chat_window.connect('destroy', self.on_destroy)
        self.chat_window.connect('focus-in-event', self.on_focus_in_event)
        self.chat_window.connect('focus-out-event', self.on_focus_out_event)

        self.chat_window.show_all()
        self.info_label.set_size_request(-1, 2)

        self.on_contact_presence_changed(contact, contact.get_presence(), "")
        contact.connect("presence-updated", self.on_contact_presence_changed)

        self.msg_logger.create_or_open()

        self.insert_last_messages()


    def on_mark_set(self, buffer, buf_iter, mark):
        """
        Callback function that is called when text is marked/selected
        in the conversation textview. It adds the marked text to the
        STRING clipboard.
        """

        if buffer.get_selection_bounds():
            start_iter, end_iter = buffer.get_selection_bounds()
            html_buffer = self.conversation_buffers.get_html_buffer()
            text = html_buffer.get_slice(start_iter.get_offset(), end_iter.get_offset(), True)

            clipboard = self.typing_textview.get_clipboard(selection="CLIPBOARD")
            clipboard.clear()
            clipboard.set_text(text)


    def on_copy_clipboard(self, textview):
        """
        Callback function that is called when the Ctrl+c keys are 
        pressed. It adds the marked text to the STRING clipboard.
        """

        text_buffer = textview.get_buffer()
        start_iter, end_iter = text_buffer.get_selection_bounds()
        html_buffer = self.conversation_buffers.get_html_buffer()
        text = html_buffer.get_slice(start_iter.get_offset(), end_iter.get_offset(), True)

        clipboard = self.typing_textview.get_clipboard(selection="CLIPBOARD")
        clipboard.clear()
        clipboard.set_text(text)

        self.conversation_textview.emit_stop_by_name("copy-clipboard")


    def on_motion_notify_event(self, textview, event):
        '''Change the cursor to the pointy finger when over an uri.'''
        pointer_x, pointer_y, nothing = textview.window.get_pointer()
        x, y = textview.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, pointer_x, pointer_y)
        tags = textview.get_iter_at_location(x, y).get_tags()

        tag_table = textview.get_buffer().get_tag_table()

        reset_cursor = True
        for tag in tags:
            if tag == tag_table.lookup('uri'):
                reset_cursor = False
                textview.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2))
                break

        if reset_cursor:
            textview.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(None)


    def on_button_press_event(self, widget, event):
        """
        Callback function that is called when the 'paste' mouse 
        button is clicked. It gets the text from the clipboard (STRING)
        and inserts it in the typing textview.
        """

        if event.button == 2:
            clipboard = self.typing_textview.get_clipboard(selection="CLIPBOARD")
            clipboard.request_contents("STRING", self.on_paste_received)

            return True

        return False


    def on_paste_received(self, clipboard, selection_data, data):
        """
        Callback function that is called when data is received
        from the STRING clipboard. It actually inserts the text
        into the typing textview.
        """

        text = clipboard.wait_for_text()

        text_buffer = self.typing_textview.get_buffer()
        end_iter = text_buffer.get_end_iter()

        if text != None:
            text_buffer.insert(end_iter, text)

        return True


    def on_paste_clipboard(self, textview):
        """
        Callback function that is called when the Ctrl+v is pressed.
        It gets the text from the clipboard (STRING) and inserts it 
        in the typing textview.
        """

        clipboard = self.typing_textview.get_clipboard(selection="CLIPBOARD")
        clipboard.request_contents("STRING", self.on_paste_received)

        self.typing_textview.emit_stop_by_name("paste-clipboard")


    def insert_last_messages(self):
        """
        Inserts (into the textbuffer) the last messages of the 
        last in-session chat with the contact in question.
        The inserted messages are shown using gray to make it
        clear that they are from last time the window was open. 
        """

        first = True

        for entry in self.last_messages:
            person, message = entry
            end_iter = self.conversation_buffers.get_end_iter()

            if first:
                first = False
            else:
                self.conversation_buffers.insert(end_iter, "\n")
                end_iter = self.conversation_buffers.get_end_iter()

            if person != None:
                self.conversation_buffers.insert_with_tags_by_name(end_iter, \
                    "%s:" % person, "bold", "gray")
                self.conversation_buffers.insert(end_iter, " ")

            self.conversation_buffers.detect_and_insert_special_text(message, ["gray"])


    def on_contact_presence_changed(self, contact, presence, msg):
        self.info_label.set_text('')
        self.info_label.set_size_request(-1, 2)

        contact_id = contact.get_uri()

        presence_image = self.local_contact_store.get_presence_icon(contact_id)
        presence_information = self.local_contact_store.get_presence_information(contact_id)

        if presence_information != None:
            presence = self.local_contact_store.get_presence(contact_id)

            red = gtk.gdk.Color(45219, 3932, 3932)
            gray = gtk.gdk.Color(32767, 32767, 32767)

            if presence == tapioca.PRESENCE_BUSY:
                self.info_label.modify_fg(gtk.STATE_NORMAL, red)
            elif presence == tapioca.PRESENCE_OFFLINE:
                self.info_label.modify_fg(gtk.STATE_NORMAL, gray)

            self.info_label.set_markup(presence_information)
            self.info_label.set_size_request(-1, -1)

        self.status_image.set_from_pixbuf(presence_image)
        self.status_image.show()


    def get_widget(self):
        """
        Returns the widget of the ChatWindow.
        """

        return self.chat_window


    def get_options_menu(self):
        """
        This functions creates a user dependend menu and pops
        it up. 
        """

        menu = gtk.Menu()

        offrecord_item = gtk.MenuItem(_("Go off the record"))
        offrecord_item.connect('activate', self.on_go_off_record)
        menu.append(offrecord_item)

        if self.local_contact_store.is_blocked(self.contact_id):
            block_item_label = _("Unblock")
        else:
            block_item_label = _("Block")

        block_item = gtk.MenuItem(block_item_label)
        block_item.connect('activate', self.on_block_contact)
        menu.append(block_item)

        return menu


    def get_text_channel(self):
        return self.channels[ChatWindow.CHANNEL_TEXT]


    def get_stream_channel(self):
        return self.channels[ChatWindow.CHANNEL_STREAM]


    def get_contact(self):
        channel = self.get_text_channel()

        if not channel:
            channel = self.get_stream_channel()

        if not channel:
            return None

        return channel.get_target()


    def on_options_button_clicked(self, widget):
        """
        Callback that pops up an option menu, containing
        a.o. the ability to block the contact of the 
        current chat session.
        """

        options_menu = self.get_options_menu()
        options_menu.show_all()

        util.popup_menu_under_button(options_menu, widget, \
                widget.get_parent_window())


    def on_call_button_clicked(self, widget=None):
        """
        Callback that ends a voip session with the
        current contact.
        """

        self.update_view(ChatWindow.VIEW_CALLING)

        contact = self.local_contact_store.get_contact(self.contact_id)
        self.main_app.start_voip_session(contact)


    def on_endcall_button_clicked(self, widget=None):
        """
        Callback that starts a voip session with the
        current contact.
        """

        self.update_view(ChatWindow.VIEW_NONE)

        contact = self.local_contact_store.get_contact(self.contact_id)
        self.main_app.stop_voip_session(contact)


    def on_answer_button_clicked(self, widget=None):
        """
        Callback that accepts a voip session with the
        calling contact.
        """

        self.update_view(ChatWindow.VIEW_TALKING)

        contact = self.local_contact_store.get_contact(self.contact_id)
        self.main_app.accept_voip_session(contact)


    def on_ignore_button_clicked(self, widget=None):
        """
        Callback that refuses to start a voip session with
        the calling contact.
        """

        self.update_view(ChatWindow.VIEW_NONE)

        contact = self.local_contact_store.get_contact(self.contact_id)
        self.main_app.stop_voip_session(contact)


    def on_emoticon_button_clicked(self, widget):
        """
        Callback that opens a dialog showing the standard
        emoticons (a limited set of often-used emoticons as
        defined in the theme.index file). When emotion is
        selected/clicked it is inserted in the buffer and
        the text view for typing gets focus, so that enter
        will send the typed text containing the inserted
        emoticon.
        """

        buffer = self.typing_textview.get_buffer()
        emoticons_menu = self.main_app.emoticons.get_emoticon_menu(buffer)

        util.popup_menu_under_button(emoticons_menu, widget, \
                widget.get_parent_window())

        self.typing_textview.grab_focus()


    def on_go_off_record(self, widget):
        self.message_logging = not self.message_logging

        if self.message_logging:
            log_msg = _("Your chat is being logged again.")
        else:
            log_msg = _("Your chat is not being logged anymore.")

        end_iter = self.conversation_buffers.get_end_iter()
        self.conversation_buffers.insert(end_iter, '\n')
        self.conversation_buffers.insert_with_tags_by_name(end_iter, log_msg, "gray")

        self.scroll_to_bottom()


    def on_block_contact(self, widget):
        contact = self.local_contact_store.get_contact(self.contact_id)
        handler_id = -1

        def do_blocked(contact, is_blocked):
            contact.disconnect(handler_id)
            self.local_contact_store.block(self.contact_id, is_blocked)
            self.local_contact_store.send_store_updated(self.contact_id)

            alias = self.local_contact_store.get_alias(self.contact_id)

            if is_blocked:
                presence_image = self.local_contact_store.get_presence_icon(self.contact_id)
                self.status_image.set_from_pixbuf(presence_image)
                self.status_image.show()

                block_msg = \
                    _("\nYou have blocked %s. You can no longer see each other online or chat together.") \
                    % alias
            else:
                self.on_contact_presence_changed(contact, contact.get_presence(), "")
                block_msg = _("\nYou have unblocked %s.") % alias

            end_iter = self.conversation_buffers.get_end_iter()
            self.conversation_buffers.insert_with_tags_by_name(end_iter, block_msg, "gray")

            self.local_contact_store.send_store_updated(self.contact_id)

        do_block = not self.local_contact_store.is_blocked(self.contact_id)
        handler_id = contact.connect('block-status-changed', do_blocked)
        contact.block(do_block)



    def set_urgency_hint(self, urgent):
        """
        Sets the urgency hint on the chat window.
        """

        self.chat_window.set_urgency_hint(urgent)


    def set_capabilities(self, capabilities):
        self.capabilities = capabilities
        self.update_view()


    def update_view(self, mode=VIEW_NONE):
        #self.volume_control.hide()
        self.volume_control.show()
        self.calling_label.hide()
        self.call_button.hide()
        self.endcall_button.hide()
        self.answer_button.hide()
        self.ignore_button.hide()

        if mode == ChatWindow.VIEW_CALLING:
            self.endcall_button.show()
            self.calling_label.show()
            self.calling_label.set_text(_("Calling..."))
        elif mode == ChatWindow.VIEW_RECEIVING_CALL:
            self.answer_button.show()
            self.ignore_button.show()
            self.calling_label.show()
            self.calling_label.set_text(_("Receiving call..."))
        elif mode == ChatWindow.VIEW_TALKING:
            print '////////////////////////////////////////////////////////////'
            channel = self.get_stream_channel()
            print 'channel: ',
            print channel
            if channel:
                streams = channel.get_streams()
                for stream in streams:
                    print 'volume: ',
                    print stream.get_volume()
                    self.volume_control.set_volume(stream.get_volume() / 100.0)

            self.volume_control.show()
            self.endcall_button.show()
            self.calling_label.show()
            self.calling_label.set_text(_("Talking..."))
        else:
            if self.capabilities & tapioca.CAPABILITY_AUDIO:
                self.call_button.show()


    def add_channel(self, channel):
        print 'chat_window.py:add_channel ------------------------'
        print channel
        if type(channel) == tapioca.TextChannel:
            channel_type = ChatWindow.CHANNEL_TEXT
        elif type(channel) == tapioca.StreamChannel:
            channel_type = ChatWindow.CHANNEL_STREAM
            streams = channel.get_streams()
            for stream in streams:
                print stream,
                print ' - ',
                print type(stream),
                print ' - ',
                print type(stream) == type(AudioStream)

        else:
            return

        self.channels[channel_type] = channel


    def store_last_message(self, sender, message):
        """
        Store the last message so that we can insert them later
        in the textview if the conversation window is opened
        again.
        """

        self.last_messages.append((sender, message))

        if len(self.last_messages) > 5:
            del(self.last_messages[0])


    def insert_message(self, message, myself_talking=False):
        """
        Inserts a message into the textbuffer, converting special
        text, applying scrolling policies, scheduling time stamps,
        applying urgency hints (when window is not focused) and
        storing the last messages.
        """

        if myself_talking:
            person = _("me")
        else:
            person = self.get_contact().get_alias()
            person = person.split("@")[0]
            person = person.split(" ")[0]

        end_iter = self.conversation_buffers.get_end_iter()

        if not self.last_who_talked == person:
            if self.conversation_buffers.get_char_count() != 0:
                self.conversation_buffers.insert(end_iter, '\n')

            self.conversation_buffers.insert_with_tags_by_name(end_iter, "%s:" % person, "bold")
            self.conversation_buffers.insert(end_iter, " ")
        else:
            self.conversation_buffers.insert(end_iter, '\n')

        self.last_who_talked = person

        need_to_apply_policy = myself_talking or self._need_to_apply_scrolling_policy()

        self.conversation_buffers.detect_and_insert_special_text(message, [])

        self.store_last_message(person, message)

        timestamp = datetime.date.today().isoformat().replace("-", "") + ":" + time.strftime("%I:%M", time.localtime())
        contact_id = self.get_contact().get_uri()
        alias = self.local_contact_store.get_alias(contact_id)
        received = not myself_talking

        if self.message_logging:
            self.msg_logger.log_message(timestamp, contact_id, alias, message, received)

        if need_to_apply_policy:
            self.scroll_to_bottom()

        ## Begging for attention.
        if not self.chat_window.is_active():
            self.chat_window.set_urgency_hint(True)

        ## Observing the time of the conversation. Format: 'Sent at 8:13 AM on Friday'
        self.last_time = time.strftime(_("Sent at %I:%M %p on %A"), time.localtime())

        if self.conversation_timeout_handler != None:
            gobject.source_remove(self.conversation_timeout_handler)

        self.conversation_timeout_handler = \
                gobject.timeout_add(self.CONVERSATION_TIMEOUT, \
                self._conversation_timeout, self.last_time)


    def send_message(self, widget, event):
        if gtk.gdk.keyval_name(event.keyval) in ('Return', 'KP_Enter'):
            if not event.state & gtk.gdk.SHIFT_MASK:
                buffer = self.typing_textview.get_buffer()
                start_iter = buffer.get_start_iter()
                end_iter = buffer.get_end_iter()

                msg = buffer.get_text(start_iter,\
                        end_iter, include_hidden_chars=False)

                if msg.strip():
                    self.get_text_channel().send(msg,\
                            tapioca.MESSAGE_TYPE_NORMAL)
                    buffer.set_text('')
                    self.get_text_channel().\
                            set_local_chat_state(tapioca.CHAT_STATE_ACTIVE)
                    if self.composing_timeout_handler is not None:
                        gobject.source_remove(self.composing_timeout_handler)

                    return True

        else:
            self.get_text_channel().\
                    set_local_chat_state(tapioca.CHAT_STATE_COMPOSING)

            def do_paused_composing():
                self.get_text_channel().\
                        set_local_chat_state(tapioca.CHAT_STATE_PAUSED)

                return False

            if self.composing_timeout_handler is not None:
                gobject.source_remove(self.composing_timeout_handler)
            self.composing_timeout_handler = \
                    gobject.timeout_add(self.COMPOSING_TIMEOUT, \
                    do_paused_composing)

        return False


    def on_contact_store_updated(self, store, contact_id):
        local_id = self.get_contact().get_uri()

        if contact_id != local_id:
            return False

        if self.local_contact_store.get_presence(contact_id) == \
            tapioca.PRESENCE_OFFLINE:
            return False

        custom_message = self.local_contact_store.get_custom_presence_message_only(contact_id)

        if custom_message != None and \
            custom_message != self.contact_custom_presence_message:
            self.contact_custom_presence_message = custom_message

            alias = self.local_contact_store.get_alias(contact_id)
            time_of_change = time.strftime("%I:%M %p", time.localtime())

            msg = _("\n%s's new status message - %s   %s") % \
                    (alias, custom_message, time_of_change)

            end_iter = self.conversation_buffers.get_end_iter()
            self.conversation_buffers.insert_with_tags_by_name(end_iter, msg, "gray")

        return False


    def scroll_to_bottom(self):
        """
        Function that scrolls the textview to the bottom.
        """

        text_buffer = self.conversation_buffers.get_text_buffer()

        end_iter = text_buffer.get_end_iter()
        text_buffer.place_cursor(end_iter)
        self.conversation_textview.scroll_mark_onscreen(text_buffer.get_insert())


    def _conversation_timeout(self, last_time):
        """
        Function that is put on a timer, which will insert the time
        of the last message in the textbuffer, if the time out is 
        reached. In the case that someone types something before
        this timeout is reset and this functions is never called.
        This is done from elsewhere.
        """

        self.conversation_timeout_handler = None

        need_to_apply_policy = self._need_to_apply_scrolling_policy()

        end_iter = self.conversation_buffers.get_end_iter()
        self.conversation_buffers.insert(end_iter, '\n')
        self.conversation_buffers.insert_with_tags_by_name(end_iter, last_time, "gray")

        self.store_last_message(None, last_time)

        if need_to_apply_policy:
            self.scroll_to_bottom()


    def _need_to_apply_scrolling_policy(self):
        end_iter = self.conversation_buffers.get_end_iter()

        big_rect = self.conversation_textview.get_iter_location(end_iter)
        visible_rect = self.conversation_textview.get_visible_rect()

        big_bottom = big_rect.y + big_rect.height
        visible_bottom = visible_rect.y + visible_rect.height

        return ((big_bottom - visible_bottom) <= ChatWindow.TEXT_SCROLLING_THRESHOLD)


    def on_destroy (self, widget):
        """
        This function is called when the chat window is closed.
        It adds a message containing the time of the last send
        message to the last_messages list and passes it along
        when it emits the 'chat-window-closed' signal.
        """

        self.msg_logger.close()

        # if a message was actually sent before.
        if self.last_time != None:
            self.store_last_message(None, self.last_time)

        # Don't try to add timestamp when the window is closed.
        if self.conversation_timeout_handler is not None:
            gobject.source_remove(self.conversation_timeout_handler)

        self.emit('chat-window-closed', self.channels, self.last_messages)

        return True


    def on_focus_in_event (self, widget, event, data=None):
        self.chat_window.set_urgency_hint(False)
        self.get_text_channel().\
                set_local_chat_state(tapioca.CHAT_STATE_ACTIVE)

        return False


    def on_focus_out_event (self, widget, event, data=None):
        self.get_text_channel().\
                set_local_chat_state(tapioca.CHAT_STATE_INACTIVE)

        return False

